{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "wdeKOEkv1Fe8"
      },
      "source": [
        "##### Copyright \u0026copy; 2020 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "cellView": "form",
        "colab": {},
        "colab_type": "code",
        "id": "c2jyGuiG1gHr"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "23R0Z9RojXYW"
      },
      "source": [
        "# TFX Keras Component Tutorial\n",
        "\n",
        "***A Component-by-Component Introduction to TensorFlow Extended (TFX)***"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "LidV2qsXm4XC"
      },
      "source": [
        "Note: We recommend running this tutorial in a Colab notebook, with no setup required!  Just click \"Run in Google Colab\".\n",
        "\n",
        "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n",
        "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/components_keras\"\u003e\n",
        "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n",
        "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/components_keras.ipynb\"\u003e\n",
        "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n",
        "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/components_keras.ipynb\"\u003e\n",
        "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n",
        "\u003c/table\u003e\u003c/div\u003e"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "KAD1tLoTm_QS"
      },
      "source": [
        "\n",
        "This Colab-based tutorial will interactively walk through each built-in component of TensorFlow Extended (TFX).\n",
        "\n",
        "It covers every step in an end-to-end machine learning pipeline, from data ingestion to pushing a model to serving.\n",
        "\n",
        "When you're done, the contents of this notebook can be automatically exported as TFX pipeline source code, which you can orchestrate with Apache Airflow and Apache Beam.\n",
        "\n",
        "Note: This notebook demonstrates the use of native Keras models in TFX pipelines. **TFX only supports the TensorFlow 2 version of Keras**.\n",
        "\n",
        "Note: This notebook and its associated APIs are **experimental** and are\n",
        "in active development.  Major changes in functionality, behavior, and\n",
        "presentation are expected."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "sfSQ-kX-MLEr"
      },
      "source": [
        "## Background\n",
        "This notebook demonstrates how to use TFX in a Jupyter/Colab environment.  Here, we walk through the Chicago Taxi example in an interactive notebook.\n",
        "\n",
        "Working in an interactive notebook is a useful way to become familiar with the structure of a TFX pipeline.  It's also useful when doing development of your own pipelines as a lightweight development environment, but you should be aware that there are differences in the way interactive notebooks are orchestrated, and how they access metadata artifacts.\n",
        "\n",
        "### Orchestration\n",
        "\n",
        "In a production deployment of TFX, you will use an orchestrator such as Apache Airflow, Kubeflow Pipelines, or Apache Beam to orchestrate a pre-defined pipeline graph of TFX components.  In an interactive notebook, the notebook itself is the orchestrator, running each TFX component as you execute the notebook cells.\n",
        "\n",
        "### Metadata\n",
        "\n",
        "In a production deployment of TFX, you will access metadata through the ML Metadata (MLMD) API.  MLMD stores metadata properties in a database such as MySQL or SQLite, and stores the metadata payloads in a persistent store such as on your filesystem.  In an interactive notebook, both properties and payloads are stored in an ephemeral SQLite database in the `/tmp` directory on the Jupyter notebook or Colab server."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "2GivNBNYjb3b"
      },
      "source": [
        "## Setup\n",
        "First, we install and import the necessary packages, set up paths, and download data."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Fmgi8ZvQkScg"
      },
      "source": [
        "### Upgrade Pip\n",
        "\n",
        "To avoid upgrading Pip in a system when running locally, check to make sure that we're running in Colab.  Local systems can of course be upgraded separately."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "as4OTe2ukSqm"
      },
      "outputs": [],
      "source": [
        "try:\n",
        "  import colab\n",
        "  !pip install --upgrade pip\n",
        "except:\n",
        "  pass"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "MZOYTt1RW4TK"
      },
      "source": [
        "### Install TFX\n",
        "\n",
        "**Note: In Google Colab, because of package updates, the first time you run this cell you must restart the runtime (Runtime \u003e Restart runtime ...).**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "S4SQA7Q5nej3"
      },
      "outputs": [],
      "source": [
        "!pip install tfx==0.22.0"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "EwT0nov5QO1M"
      },
      "source": [
        "## Did you restart the runtime?\n",
        "\n",
        "If you are using Google Colab, the first time that you run the cell above, you must restart the runtime (Runtime \u003e Restart runtime ...). This is because of the way that Colab loads packages."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "N-ePgV0Lj68Q"
      },
      "source": [
        "### Import packages\n",
        "We import necessary packages, including standard TFX component classes."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "YIqpWK9efviJ"
      },
      "outputs": [],
      "source": [
        "import os\n",
        "import pprint\n",
        "import tempfile\n",
        "import urllib\n",
        "\n",
        "import absl\n",
        "import tensorflow as tf\n",
        "import tensorflow_model_analysis as tfma\n",
        "tf.get_logger().propagate = False\n",
        "pp = pprint.PrettyPrinter()\n",
        "\n",
        "import tfx\n",
        "from tfx.components import CsvExampleGen\n",
        "from tfx.components import Evaluator\n",
        "from tfx.components import ExampleValidator\n",
        "from tfx.components import Pusher\n",
        "from tfx.components import ResolverNode\n",
        "from tfx.components import SchemaGen\n",
        "from tfx.components import StatisticsGen\n",
        "from tfx.components import Trainer\n",
        "from tfx.components import Transform\n",
        "from tfx.components.base import executor_spec\n",
        "from tfx.components.trainer.executor import GenericExecutor\n",
        "from tfx.dsl.experimental import latest_blessed_model_resolver\n",
        "from tfx.orchestration import metadata\n",
        "from tfx.orchestration import pipeline\n",
        "from tfx.orchestration.experimental.interactive.interactive_context import InteractiveContext\n",
        "from tfx.proto import pusher_pb2\n",
        "from tfx.proto import trainer_pb2\n",
        "from tfx.types import Channel\n",
        "from tfx.types.standard_artifacts import Model\n",
        "from tfx.types.standard_artifacts import ModelBlessing\n",
        "from tfx.utils.dsl_utils import external_input\n",
        "\n",
        "\n",
        "%load_ext tfx.orchestration.experimental.interactive.notebook_extensions.skip"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "wCZTHRy0N1D6"
      },
      "source": [
        "Let's check the library versions."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "eZ4K18_DN2D8"
      },
      "outputs": [],
      "source": [
        "print('TensorFlow version: {}'.format(tf.__version__))\n",
        "print('TFX version: {}'.format(tfx.__version__))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "ufJKQ6OvkJlY"
      },
      "source": [
        "### Set up pipeline paths"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "ad5JLpKbf6sN"
      },
      "outputs": [],
      "source": [
        "# This is the root directory for your TFX pip package installation.\n",
        "_tfx_root = tfx.__path__[0]\n",
        "\n",
        "# This is the directory containing the TFX Chicago Taxi Pipeline example.\n",
        "_taxi_root = os.path.join(_tfx_root, 'examples/chicago_taxi_pipeline')\n",
        "\n",
        "# This is the path where your model will be pushed for serving.\n",
        "_serving_model_dir = os.path.join(\n",
        "    tempfile.mkdtemp(), 'serving_model/taxi_simple')\n",
        "\n",
        "# Set up logging.\n",
        "absl.logging.set_verbosity(absl.logging.INFO)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "n2cMMAbSkGfX"
      },
      "source": [
        "### Download example data\n",
        "We download the example dataset for use in our TFX pipeline.\n",
        "\n",
        "The dataset we're using is the [Taxi Trips dataset](https://data.cityofchicago.org/Transportation/Taxi-Trips/wrvz-psew) released by the City of Chicago. The columns in this dataset are:\n",
        "\n",
        "\u003ctable\u003e\n",
        "\u003ctr\u003e\u003ctd\u003epickup_community_area\u003c/td\u003e\u003ctd\u003efare\u003c/td\u003e\u003ctd\u003etrip_start_month\u003c/td\u003e\u003c/tr\u003e\n",
        "\u003ctr\u003e\u003ctd\u003etrip_start_hour\u003c/td\u003e\u003ctd\u003etrip_start_day\u003c/td\u003e\u003ctd\u003etrip_start_timestamp\u003c/td\u003e\u003c/tr\u003e\n",
        "\u003ctr\u003e\u003ctd\u003epickup_latitude\u003c/td\u003e\u003ctd\u003epickup_longitude\u003c/td\u003e\u003ctd\u003edropoff_latitude\u003c/td\u003e\u003c/tr\u003e\n",
        "\u003ctr\u003e\u003ctd\u003edropoff_longitude\u003c/td\u003e\u003ctd\u003etrip_miles\u003c/td\u003e\u003ctd\u003epickup_census_tract\u003c/td\u003e\u003c/tr\u003e\n",
        "\u003ctr\u003e\u003ctd\u003edropoff_census_tract\u003c/td\u003e\u003ctd\u003epayment_type\u003c/td\u003e\u003ctd\u003ecompany\u003c/td\u003e\u003c/tr\u003e\n",
        "\u003ctr\u003e\u003ctd\u003etrip_seconds\u003c/td\u003e\u003ctd\u003edropoff_community_area\u003c/td\u003e\u003ctd\u003etips\u003c/td\u003e\u003c/tr\u003e\n",
        "\u003c/table\u003e\n",
        "\n",
        "With this dataset, we will build a model that predicts the `tips` of a trip."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "BywX6OUEhAqn"
      },
      "outputs": [],
      "source": [
        "_data_root = tempfile.mkdtemp(prefix='tfx-data')\n",
        "DATA_PATH = 'https://raw.githubusercontent.com/tensorflow/tfx/master/tfx/examples/chicago_taxi_pipeline/data/simple/data.csv'\n",
        "_data_filepath = os.path.join(_data_root, \"data.csv\")\n",
        "urllib.request.urlretrieve(DATA_PATH, _data_filepath)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "blZC1sIQOWfH"
      },
      "source": [
        "Take a quick look at the CSV file."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "c5YPeLPFOXaD"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "!head {_data_filepath}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "QioyhunCImwE"
      },
      "source": [
        "*Disclaimer: This site provides applications using data that has been modified for use from its original source, www.cityofchicago.org, the official website of the City of Chicago. The City of Chicago makes no claims as to the content, accuracy, timeliness, or completeness of any of the data provided at this site. The data provided at this site is subject to change at any time. It is understood that the data provided at this site is being used at one’s own risk.*"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "8ONIE_hdkPS4"
      },
      "source": [
        "### Create the InteractiveContext\n",
        "Last, we create an InteractiveContext, which will allow us to run TFX components interactively in this notebook."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "0Rh6K5sUf9dd"
      },
      "outputs": [],
      "source": [
        "# Here, we create an InteractiveContext using default parameters. This will\n",
        "# use a temporary directory with an ephemeral ML Metadata database instance.\n",
        "# To use your own pipeline root or database, the optional properties\n",
        "# `pipeline_root` and `metadata_connection_config` may be passed to\n",
        "# InteractiveContext. Calls to InteractiveContext are no-ops outside of the\n",
        "# notebook.\n",
        "context = InteractiveContext()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "HdQWxfsVkzdJ"
      },
      "source": [
        "## Run TFX components interactively\n",
        "In the cells that follow, we create TFX components one-by-one, run each of them, and visualize their output artifacts."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "L9fwt9gQk3BR"
      },
      "source": [
        "### ExampleGen\n",
        "\n",
        "The `ExampleGen` component is usually at the start of a TFX pipeline. It will:\n",
        "\n",
        "1.   Split data into training and evaluation sets (by default, 2/3 training + 1/3 eval)\n",
        "2.   Convert data into the `tf.Example` format\n",
        "3.   Copy data into the `_tfx_root` directory for other components to access\n",
        "\n",
        "`ExampleGen` takes as input the path to your data source. In our case, this is the `_data_root` path that contains the downloaded CSV.\n",
        "\n",
        "Note: In this notebook, we can instantiate components one-by-one and run them with `InteractiveContext.run()`. By contrast, in a production setting, we would specify all the components upfront in a `Pipeline` to pass to the orchestrator (see the \"Export to Pipeline\" section)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "PyXjuMt8f-9u"
      },
      "outputs": [],
      "source": [
        "example_gen = CsvExampleGen(input=external_input(_data_root))\n",
        "context.run(example_gen)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "OqCoZh7KPUm9"
      },
      "source": [
        "Let's examine the output artifacts of `ExampleGen`. This component produces two artifacts, training examples and evaluation examples:\n",
        "\n",
        "Note: The `%%skip_for_export` cell magic will omit the contents of this cell in the exported pipeline file (see the \"Export to pipeline\" section). This is useful for notebook-specific code that you don't want to run in an orchestrated pipeline."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "880KkTAkPeUg"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "artifact = example_gen.outputs['examples'].get()[0]\n",
        "print(artifact.split_names, artifact.uri)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "J6vcbW_wPqvl"
      },
      "source": [
        "We can also take a look at the first three training examples:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "H4XIXjiCPwzQ"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "# Get the URI of the output artifact representing the training examples, which is a directory\n",
        "train_uri = os.path.join(example_gen.outputs['examples'].get()[0].uri, 'train')\n",
        "\n",
        "# Get the list of files in this directory (all compressed TFRecord files)\n",
        "tfrecord_filenames = [os.path.join(train_uri, name)\n",
        "                      for name in os.listdir(train_uri)]\n",
        "\n",
        "# Create a `TFRecordDataset` to read these files\n",
        "dataset = tf.data.TFRecordDataset(tfrecord_filenames, compression_type=\"GZIP\")\n",
        "\n",
        "# Iterate over the first 3 records and decode them.\n",
        "for tfrecord in dataset.take(3):\n",
        "  serialized_example = tfrecord.numpy()\n",
        "  example = tf.train.Example()\n",
        "  example.ParseFromString(serialized_example)\n",
        "  pp.pprint(example)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "2gluYjccf-IP"
      },
      "source": [
        "Now that `ExampleGen` has finished ingesting the data, the next step is data analysis."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "csM6BFhtk5Aa"
      },
      "source": [
        "### StatisticsGen\n",
        "The `StatisticsGen` component computes statistics over your dataset for data analysis, as well as for use in downstream components. It uses the [TensorFlow Data Validation](https://www.tensorflow.org/tfx/data_validation/get_started) library.\n",
        "\n",
        "`StatisticsGen` takes as input the dataset we just ingested using `ExampleGen`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "MAscCCYWgA-9"
      },
      "outputs": [],
      "source": [
        "statistics_gen = StatisticsGen(\n",
        "    examples=example_gen.outputs['examples'])\n",
        "context.run(statistics_gen)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "HLI6cb_5WugZ"
      },
      "source": [
        "After `StatisticsGen` finishes running, we can visualize the outputted statistics. Try playing with the different plots!"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "tLjXy7K6Tp_G"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "context.show(statistics_gen.outputs['statistics'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "HLKLTO9Nk60p"
      },
      "source": [
        "### SchemaGen\n",
        "\n",
        "The `SchemaGen` component generates a schema based on your data statistics. (A schema defines the expected bounds, types, and properties of the features in your dataset.) It also uses the [TensorFlow Data Validation](https://www.tensorflow.org/tfx/data_validation/get_started) library.\n",
        "\n",
        "`SchemaGen` will take as input the statistics that we generated with `StatisticsGen`, looking at the training split by default."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "ygQvZ6hsiQ_J"
      },
      "outputs": [],
      "source": [
        "schema_gen = SchemaGen(\n",
        "    statistics=statistics_gen.outputs['statistics'],\n",
        "    infer_feature_shape=False)\n",
        "context.run(schema_gen)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "zi6TxTUKXM6b"
      },
      "source": [
        "After `SchemaGen` finishes running, we can visualize the generated schema as a table."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "Ec9vqDXpXeMb"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "context.show(schema_gen.outputs['schema'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "kZWWdbA-m7zp"
      },
      "source": [
        "Each feature in your dataset shows up as a row in the schema table, alongside its properties. The schema also captures all the values that a categorical feature takes on, denoted as its domain.\n",
        "\n",
        "To learn more about schemas, see [the SchemaGen documentation](https://www.tensorflow.org/tfx/guide/schemagen)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "V1qcUuO9k9f8"
      },
      "source": [
        "### ExampleValidator\n",
        "The `ExampleValidator` component detects anomalies in your data, based on the expectations defined by the schema. It also uses the [TensorFlow Data Validation](https://www.tensorflow.org/tfx/data_validation/get_started) library.\n",
        "\n",
        "`ExampleValidator` will take as input the statistics from `StatisticsGen`, and the schema from `SchemaGen`.\n",
        "\n",
        "By default, it compares the statistics from the evaluation split to the schema from the training split."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "XRlRUuGgiXks"
      },
      "outputs": [],
      "source": [
        "example_validator = ExampleValidator(\n",
        "    statistics=statistics_gen.outputs['statistics'],\n",
        "    schema=schema_gen.outputs['schema'])\n",
        "context.run(example_validator)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "855mrHgJcoer"
      },
      "source": [
        "After `ExampleValidator` finishes running, we can visualize the anomalies as a table."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "TDyAAozQcrk3"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "context.show(example_validator.outputs['anomalies'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "znMoJj60ybZx"
      },
      "source": [
        "In the anomalies table, we can see that the `company` feature takes on new values that were not in the training split. This information can be used to debug model performance, understand how your data evolves over time, and identify data errors.\n",
        "\n",
        "In our case, this `company` anomaly is innocuous, but the `payment_type` could be fixed. For now we move on to the next step of transforming the data."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "JPViEz5RlA36"
      },
      "source": [
        "### Transform\n",
        "The `Transform` component performs feature engineering for both training and serving. It uses the [TensorFlow Transform](https://www.tensorflow.org/tfx/transform/get_started) library.\n",
        "\n",
        "`Transform` will take as input the data from `ExampleGen`, the schema from `SchemaGen`, as well as a module that contains user-defined Transform code.\n",
        "\n",
        "Let's see an example of user-defined Transform code below (for an introduction to the TensorFlow Transform APIs, [see the tutorial](https://www.tensorflow.org/tfx/tutorials/transform/simple)). First, we define a few constants for feature engineering:\n",
        "\n",
        "Note: The `%%writefile` cell magic will save the contents of the cell as a `.py` file on disk. This allows the `Transform` component to load your code as a module.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "PuNSiUKb4YJf"
      },
      "outputs": [],
      "source": [
        "_taxi_constants_module_file = 'taxi_constants.py'"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "HPjhXuIF4YJh"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "%%writefile {_taxi_constants_module_file}\n",
        "\n",
        "# Categorical features are assumed to each have a maximum value in the dataset.\n",
        "MAX_CATEGORICAL_FEATURE_VALUES = [24, 31, 12]\n",
        "\n",
        "CATEGORICAL_FEATURE_KEYS = [\n",
        "    'trip_start_hour', 'trip_start_day', 'trip_start_month',\n",
        "    'pickup_census_tract', 'dropoff_census_tract', 'pickup_community_area',\n",
        "    'dropoff_community_area'\n",
        "]\n",
        "\n",
        "DENSE_FLOAT_FEATURE_KEYS = ['trip_miles', 'fare', 'trip_seconds']\n",
        "\n",
        "# Number of buckets used by tf.transform for encoding each feature.\n",
        "FEATURE_BUCKET_COUNT = 10\n",
        "\n",
        "BUCKET_FEATURE_KEYS = [\n",
        "    'pickup_latitude', 'pickup_longitude', 'dropoff_latitude',\n",
        "    'dropoff_longitude'\n",
        "]\n",
        "\n",
        "# Number of vocabulary terms used for encoding VOCAB_FEATURES by tf.transform\n",
        "VOCAB_SIZE = 1000\n",
        "\n",
        "# Count of out-of-vocab buckets in which unrecognized VOCAB_FEATURES are hashed.\n",
        "OOV_SIZE = 10\n",
        "\n",
        "VOCAB_FEATURE_KEYS = [\n",
        "    'payment_type',\n",
        "    'company',\n",
        "]\n",
        "\n",
        "# Keys\n",
        "LABEL_KEY = 'tips'\n",
        "FARE_KEY = 'fare'\n",
        "\n",
        "def transformed_name(key):\n",
        "  return key + '_xf'"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Duj2Ax5z4YJl"
      },
      "source": [
        "Next, we write a `preprocessing_fn` that takes in raw data as input, and returns transformed features that our model can train on:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "4AJ9hBs94YJm"
      },
      "outputs": [],
      "source": [
        "_taxi_transform_module_file = 'taxi_transform.py'"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "MYmxxx9A4YJn"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "%%writefile {_taxi_transform_module_file}\n",
        "\n",
        "import tensorflow as tf\n",
        "import tensorflow_transform as tft\n",
        "\n",
        "import taxi_constants\n",
        "\n",
        "_DENSE_FLOAT_FEATURE_KEYS = taxi_constants.DENSE_FLOAT_FEATURE_KEYS\n",
        "_VOCAB_FEATURE_KEYS = taxi_constants.VOCAB_FEATURE_KEYS\n",
        "_VOCAB_SIZE = taxi_constants.VOCAB_SIZE\n",
        "_OOV_SIZE = taxi_constants.OOV_SIZE\n",
        "_FEATURE_BUCKET_COUNT = taxi_constants.FEATURE_BUCKET_COUNT\n",
        "_BUCKET_FEATURE_KEYS = taxi_constants.BUCKET_FEATURE_KEYS\n",
        "_CATEGORICAL_FEATURE_KEYS = taxi_constants.CATEGORICAL_FEATURE_KEYS\n",
        "_FARE_KEY = taxi_constants.FARE_KEY\n",
        "_LABEL_KEY = taxi_constants.LABEL_KEY\n",
        "_transformed_name = taxi_constants.transformed_name\n",
        "\n",
        "\n",
        "def preprocessing_fn(inputs):\n",
        "  \"\"\"tf.transform's callback function for preprocessing inputs.\n",
        "  Args:\n",
        "    inputs: map from feature keys to raw not-yet-transformed features.\n",
        "  Returns:\n",
        "    Map from string feature key to transformed feature operations.\n",
        "  \"\"\"\n",
        "  outputs = {}\n",
        "  for key in _DENSE_FLOAT_FEATURE_KEYS:\n",
        "    # Preserve this feature as a dense float, setting nan's to the mean.\n",
        "    outputs[_transformed_name(key)] = tft.scale_to_z_score(\n",
        "        _fill_in_missing(inputs[key]))\n",
        "\n",
        "  for key in _VOCAB_FEATURE_KEYS:\n",
        "    # Build a vocabulary for this feature.\n",
        "    outputs[_transformed_name(key)] = tft.compute_and_apply_vocabulary(\n",
        "        _fill_in_missing(inputs[key]),\n",
        "        top_k=_VOCAB_SIZE,\n",
        "        num_oov_buckets=_OOV_SIZE)\n",
        "\n",
        "  for key in _BUCKET_FEATURE_KEYS:\n",
        "    outputs[_transformed_name(key)] = tft.bucketize(\n",
        "        _fill_in_missing(inputs[key]), _FEATURE_BUCKET_COUNT)\n",
        "\n",
        "  for key in _CATEGORICAL_FEATURE_KEYS:\n",
        "    outputs[_transformed_name(key)] = _fill_in_missing(inputs[key])\n",
        "\n",
        "  # Was this passenger a big tipper?\n",
        "  taxi_fare = _fill_in_missing(inputs[_FARE_KEY])\n",
        "  tips = _fill_in_missing(inputs[_LABEL_KEY])\n",
        "  outputs[_transformed_name(_LABEL_KEY)] = tf.where(\n",
        "      tf.math.is_nan(taxi_fare),\n",
        "      tf.cast(tf.zeros_like(taxi_fare), tf.int64),\n",
        "      # Test if the tip was \u003e 20% of the fare.\n",
        "      tf.cast(\n",
        "          tf.greater(tips, tf.multiply(taxi_fare, tf.constant(0.2))), tf.int64))\n",
        "\n",
        "  return outputs\n",
        "\n",
        "\n",
        "def _fill_in_missing(x):\n",
        "  \"\"\"Replace missing values in a SparseTensor.\n",
        "  Fills in missing values of `x` with '' or 0, and converts to a dense tensor.\n",
        "  Args:\n",
        "    x: A `SparseTensor` of rank 2.  Its dense shape should have size at most 1\n",
        "      in the second dimension.\n",
        "  Returns:\n",
        "    A rank 1 tensor where missing values of `x` have been filled in.\n",
        "  \"\"\"\n",
        "  default_value = '' if x.dtype == tf.string else 0\n",
        "  return tf.squeeze(\n",
        "      tf.sparse.to_dense(\n",
        "          tf.SparseTensor(x.indices, x.values, [x.dense_shape[0], 1]),\n",
        "          default_value),\n",
        "      axis=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "wgbmZr3sgbWW"
      },
      "source": [
        "Now, we pass in this feature engineering code to the `Transform` component and run it to transform your data."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "jHfhth_GiZI9"
      },
      "outputs": [],
      "source": [
        "transform = Transform(\n",
        "    examples=example_gen.outputs['examples'],\n",
        "    schema=schema_gen.outputs['schema'],\n",
        "    module_file=os.path.abspath(_taxi_transform_module_file))\n",
        "context.run(transform)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "fwAwb4rARRQ2"
      },
      "source": [
        "Let's examine the output artifacts of `Transform`. This component produces two types of outputs:\n",
        "\n",
        "* `transform_graph` is the graph that can perform the preprocessing operations (this graph will be included in the serving and evaluation models).\n",
        "* `transformed_examples` represents the preprocessed training and evaluation data."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "SClrAaEGR1O5"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "transform.outputs"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "vyFkBd9AR1sy"
      },
      "source": [
        "Take a peek at the `transform_graph` artifact.  It points to a directory containing three subdirectories."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "5tRw4DneR3i7"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "train_uri = transform.outputs['transform_graph'].get()[0].uri\n",
        "os.listdir(train_uri)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "4fqV54CIR6Pu"
      },
      "source": [
        "The `transformed_metadata` subdirectory contains the schema of the preprocessed data. The `transform_fn` subdirectory contains the actual preprocessing graph. The `metadata` subdirectory contains the schema of the original data.\n",
        "\n",
        "We can also take a look at the first three transformed examples:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "pwbW2zPKR_S4"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "# Get the URI of the output artifact representing the transformed examples, which is a directory\n",
        "train_uri = os.path.join(transform.outputs['transformed_examples'].get()[0].uri, 'train')\n",
        "\n",
        "# Get the list of files in this directory (all compressed TFRecord files)\n",
        "tfrecord_filenames = [os.path.join(train_uri, name)\n",
        "                      for name in os.listdir(train_uri)]\n",
        "\n",
        "# Create a `TFRecordDataset` to read these files\n",
        "dataset = tf.data.TFRecordDataset(tfrecord_filenames, compression_type=\"GZIP\")\n",
        "\n",
        "# Iterate over the first 3 records and decode them.\n",
        "for tfrecord in dataset.take(3):\n",
        "  serialized_example = tfrecord.numpy()\n",
        "  example = tf.train.Example()\n",
        "  example.ParseFromString(serialized_example)\n",
        "  pp.pprint(example)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "q_b_V6eN4f69"
      },
      "source": [
        "After the `Transform` component has transformed your data into features, and the next step is to train a model."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "OBJFtnl6lCg9"
      },
      "source": [
        "### Trainer\n",
        "The `Trainer` component will train a model that you define in TensorFlow. Default Trainer support Estimator API, to use Keras API, you need to specify [Generic Trainer](https://github.com/tensorflow/community/blob/master/rfcs/20200117-tfx-generic-trainer.md) by setup `custom_executor_spec=executor_spec.ExecutorClassSpec(GenericExecutor)` in Trainer's contructor.\n",
        "\n",
        "`Trainer` takes as input the schema from `SchemaGen`, the transformed data and graph from `Transform`, training parameters, as well as a module that contains user-defined model code.\n",
        "\n",
        "Let's see an example of user-defined model code below (for an introduction to the TensorFlow Keras APIs, [see the tutorial](https://www.tensorflow.org/guide/keras)):"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "N1376oq04YJt"
      },
      "outputs": [],
      "source": [
        "_taxi_trainer_module_file = 'taxi_trainer.py'"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "nf9UuNng4YJu"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "%%writefile {_taxi_trainer_module_file}\n",
        "\n",
        "from typing import List, Text\n",
        "\n",
        "import os\n",
        "import absl\n",
        "import datetime\n",
        "import tensorflow as tf\n",
        "import tensorflow_transform as tft\n",
        "\n",
        "from tfx.components.trainer.executor import TrainerFnArgs\n",
        "\n",
        "import taxi_constants\n",
        "\n",
        "_DENSE_FLOAT_FEATURE_KEYS = taxi_constants.DENSE_FLOAT_FEATURE_KEYS\n",
        "_VOCAB_FEATURE_KEYS = taxi_constants.VOCAB_FEATURE_KEYS\n",
        "_VOCAB_SIZE = taxi_constants.VOCAB_SIZE\n",
        "_OOV_SIZE = taxi_constants.OOV_SIZE\n",
        "_FEATURE_BUCKET_COUNT = taxi_constants.FEATURE_BUCKET_COUNT\n",
        "_BUCKET_FEATURE_KEYS = taxi_constants.BUCKET_FEATURE_KEYS\n",
        "_CATEGORICAL_FEATURE_KEYS = taxi_constants.CATEGORICAL_FEATURE_KEYS\n",
        "_MAX_CATEGORICAL_FEATURE_VALUES = taxi_constants.MAX_CATEGORICAL_FEATURE_VALUES\n",
        "_LABEL_KEY = taxi_constants.LABEL_KEY\n",
        "_transformed_name = taxi_constants.transformed_name\n",
        "\n",
        "\n",
        "def _transformed_names(keys):\n",
        "  return [_transformed_name(key) for key in keys]\n",
        "\n",
        "\n",
        "def _gzip_reader_fn(filenames):\n",
        "  \"\"\"Small utility returning a record reader that can read gzip'ed files.\"\"\"\n",
        "  return tf.data.TFRecordDataset(\n",
        "      filenames,\n",
        "      compression_type='GZIP')\n",
        "\n",
        "\n",
        "def _get_serve_tf_examples_fn(model, tf_transform_output):\n",
        "  \"\"\"Returns a function that parses a serialized tf.Example and applies TFT.\"\"\"\n",
        "\n",
        "  model.tft_layer = tf_transform_output.transform_features_layer()\n",
        "\n",
        "  @tf.function\n",
        "  def serve_tf_examples_fn(serialized_tf_examples):\n",
        "    \"\"\"Returns the output to be used in the serving signature.\"\"\"\n",
        "    feature_spec = tf_transform_output.raw_feature_spec()\n",
        "    feature_spec.pop(_LABEL_KEY)\n",
        "    parsed_features = tf.io.parse_example(serialized_tf_examples, feature_spec)\n",
        "    transformed_features = model.tft_layer(parsed_features)\n",
        "    return model(transformed_features)\n",
        "\n",
        "  return serve_tf_examples_fn\n",
        "\n",
        "\n",
        "def _input_fn(file_pattern: List[Text],\n",
        "              tf_transform_output: tft.TFTransformOutput,\n",
        "              batch_size: int = 200) -\u003e tf.data.Dataset:\n",
        "  \"\"\"Generates features and label for tuning/training.\n",
        "\n",
        "  Args:\n",
        "    file_pattern: List of paths or patterns of input tfrecord files.\n",
        "    tf_transform_output: A TFTransformOutput.\n",
        "    batch_size: representing the number of consecutive elements of returned\n",
        "      dataset to combine in a single batch\n",
        "\n",
        "  Returns:\n",
        "    A dataset that contains (features, indices) tuple where features is a\n",
        "      dictionary of Tensors, and indices is a single Tensor of label indices.\n",
        "  \"\"\"\n",
        "  transformed_feature_spec = (\n",
        "      tf_transform_output.transformed_feature_spec().copy())\n",
        "\n",
        "  dataset = tf.data.experimental.make_batched_features_dataset(\n",
        "      file_pattern=file_pattern,\n",
        "      batch_size=batch_size,\n",
        "      features=transformed_feature_spec,\n",
        "      reader=_gzip_reader_fn,\n",
        "      label_key=_transformed_name(_LABEL_KEY))\n",
        "\n",
        "  return dataset\n",
        "\n",
        "\n",
        "def _build_keras_model(hidden_units: List[int] = None) -\u003e tf.keras.Model:\n",
        "  \"\"\"Creates a DNN Keras model for classifying taxi data.\n",
        "\n",
        "  Args:\n",
        "    hidden_units: [int], the layer sizes of the DNN (input layer first).\n",
        "\n",
        "  Returns:\n",
        "    A keras Model.\n",
        "  \"\"\"\n",
        "  real_valued_columns = [\n",
        "      tf.feature_column.numeric_column(key, shape=())\n",
        "      for key in _transformed_names(_DENSE_FLOAT_FEATURE_KEYS)\n",
        "  ]\n",
        "  categorical_columns = [\n",
        "      tf.feature_column.categorical_column_with_identity(\n",
        "          key, num_buckets=_VOCAB_SIZE + _OOV_SIZE, default_value=0)\n",
        "      for key in _transformed_names(_VOCAB_FEATURE_KEYS)\n",
        "  ]\n",
        "  categorical_columns += [\n",
        "      tf.feature_column.categorical_column_with_identity(\n",
        "          key, num_buckets=_FEATURE_BUCKET_COUNT, default_value=0)\n",
        "      for key in _transformed_names(_BUCKET_FEATURE_KEYS)\n",
        "  ]\n",
        "  categorical_columns += [\n",
        "      tf.feature_column.categorical_column_with_identity(  # pylint: disable=g-complex-comprehension\n",
        "          key,\n",
        "          num_buckets=num_buckets,\n",
        "          default_value=0) for key, num_buckets in zip(\n",
        "              _transformed_names(_CATEGORICAL_FEATURE_KEYS),\n",
        "              _MAX_CATEGORICAL_FEATURE_VALUES)\n",
        "  ]\n",
        "  indicator_column = [\n",
        "      tf.feature_column.indicator_column(categorical_column)\n",
        "      for categorical_column in categorical_columns\n",
        "  ]\n",
        "\n",
        "  model = _wide_and_deep_classifier(\n",
        "      # TODO(b/139668410) replace with premade wide_and_deep keras model\n",
        "      wide_columns=indicator_column,\n",
        "      deep_columns=real_valued_columns,\n",
        "      dnn_hidden_units=hidden_units or [100, 70, 50, 25])\n",
        "  return model\n",
        "\n",
        "\n",
        "def _wide_and_deep_classifier(wide_columns, deep_columns, dnn_hidden_units):\n",
        "  \"\"\"Build a simple keras wide and deep model.\n",
        "\n",
        "  Args:\n",
        "    wide_columns: Feature columns wrapped in indicator_column for wide (linear)\n",
        "      part of the model.\n",
        "    deep_columns: Feature columns for deep part of the model.\n",
        "    dnn_hidden_units: [int], the layer sizes of the hidden DNN.\n",
        "\n",
        "  Returns:\n",
        "    A Wide and Deep Keras model\n",
        "  \"\"\"\n",
        "  # Following values are hard coded for simplicity in this example,\n",
        "  # However prefarably they should be passsed in as hparams.\n",
        "\n",
        "  # Keras needs the feature definitions at compile time.\n",
        "  # TODO(b/139081439): Automate generation of input layers from FeatureColumn.\n",
        "  input_layers = {\n",
        "      colname: tf.keras.layers.Input(name=colname, shape=(), dtype=tf.float32)\n",
        "      for colname in _transformed_names(_DENSE_FLOAT_FEATURE_KEYS)\n",
        "  }\n",
        "  input_layers.update({\n",
        "      colname: tf.keras.layers.Input(name=colname, shape=(), dtype='int32')\n",
        "      for colname in _transformed_names(_VOCAB_FEATURE_KEYS)\n",
        "  })\n",
        "  input_layers.update({\n",
        "      colname: tf.keras.layers.Input(name=colname, shape=(), dtype='int32')\n",
        "      for colname in _transformed_names(_BUCKET_FEATURE_KEYS)\n",
        "  })\n",
        "  input_layers.update({\n",
        "      colname: tf.keras.layers.Input(name=colname, shape=(), dtype='int32')\n",
        "      for colname in _transformed_names(_CATEGORICAL_FEATURE_KEYS)\n",
        "  })\n",
        "\n",
        "  # TODO(b/144500510): SparseFeatures for feature columns + Keras.\n",
        "  deep = tf.keras.layers.DenseFeatures(deep_columns)(input_layers)\n",
        "  for numnodes in dnn_hidden_units:\n",
        "    deep = tf.keras.layers.Dense(numnodes)(deep)\n",
        "  wide = tf.keras.layers.DenseFeatures(wide_columns)(input_layers)\n",
        "\n",
        "  output = tf.keras.layers.Dense(\n",
        "      1, activation='sigmoid')(\n",
        "          tf.keras.layers.concatenate([deep, wide]))\n",
        "\n",
        "  model = tf.keras.Model(input_layers, output)\n",
        "  model.compile(\n",
        "      loss='binary_crossentropy',\n",
        "      optimizer=tf.keras.optimizers.Adam(lr=0.001),\n",
        "      metrics=[tf.keras.metrics.BinaryAccuracy()])\n",
        "  model.summary(print_fn=absl.logging.info)\n",
        "  return model\n",
        "\n",
        "\n",
        "# TFX Trainer will call this function.\n",
        "def run_fn(fn_args: TrainerFnArgs):\n",
        "  \"\"\"Train the model based on given args.\n",
        "\n",
        "  Args:\n",
        "    fn_args: Holds args used to train the model as name/value pairs.\n",
        "  \"\"\"\n",
        "  # Number of nodes in the first layer of the DNN\n",
        "  first_dnn_layer_size = 100\n",
        "  num_dnn_layers = 4\n",
        "  dnn_decay_factor = 0.7\n",
        "\n",
        "  tf_transform_output = tft.TFTransformOutput(fn_args.transform_output)\n",
        "\n",
        "  train_dataset = _input_fn(fn_args.train_files, tf_transform_output, 40)\n",
        "  eval_dataset = _input_fn(fn_args.eval_files, tf_transform_output, 40)\n",
        "\n",
        "  model = _build_keras_model(\n",
        "      # Construct layers sizes with exponetial decay\n",
        "      hidden_units=[\n",
        "          max(2, int(first_dnn_layer_size * dnn_decay_factor**i))\n",
        "          for i in range(num_dnn_layers)\n",
        "      ])\n",
        "\n",
        "  # TODO(b/158106209): use ModelRun instead of Model artifact for logging.\n",
        "  log_dir = os.path.join(os.path.dirname(fn_args.serving_model_dir), 'logs')\n",
        "  tensorboard_callback = tf.keras.callbacks.TensorBoard(\n",
        "      log_dir=log_dir, update_freq='batch')\n",
        "  model.fit(\n",
        "      train_dataset,\n",
        "      steps_per_epoch=fn_args.train_steps,\n",
        "      validation_data=eval_dataset,\n",
        "      validation_steps=fn_args.eval_steps,\n",
        "      callbacks=[tensorboard_callback])\n",
        "\n",
        "  signatures = {\n",
        "      'serving_default':\n",
        "          _get_serve_tf_examples_fn(model,\n",
        "                                    tf_transform_output).get_concrete_function(\n",
        "                                        tf.TensorSpec(\n",
        "                                            shape=[None],\n",
        "                                            dtype=tf.string,\n",
        "                                            name='examples')),\n",
        "  }\n",
        "  model.save(fn_args.serving_model_dir, save_format='tf', signatures=signatures)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "GY4yTRaX4YJx"
      },
      "source": [
        "Now, we pass in this model code to the `Trainer` component and run it to train the model."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "429-vvCWibO0"
      },
      "outputs": [],
      "source": [
        "trainer = Trainer(\n",
        "    module_file=os.path.abspath(_taxi_trainer_module_file),\n",
        "    custom_executor_spec=executor_spec.ExecutorClassSpec(GenericExecutor),\n",
        "    examples=transform.outputs['transformed_examples'],\n",
        "    transform_graph=transform.outputs['transform_graph'],\n",
        "    schema=schema_gen.outputs['schema'],\n",
        "    train_args=trainer_pb2.TrainArgs(num_steps=10000),\n",
        "    eval_args=trainer_pb2.EvalArgs(num_steps=5000))\n",
        "context.run(trainer)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "6Cql1G35StJp"
      },
      "source": [
        "#### Analyze Training with TensorBoard\n",
        "Take a peek at the trainer artifact. It points to a directory containing the model subdirectories."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "bXe62WE0S0Ek"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "model_artifact_dir = trainer.outputs['model'].get()[0].uri\n",
        "pp.pprint(os.listdir(model_artifact_dir))\n",
        "model_dir = os.path.join(model_artifact_dir, 'serving_model_dir')\n",
        "pp.pprint(os.listdir(model_dir))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "DfjOmSro6Q3Y"
      },
      "source": [
        "Optionally, we can connect TensorBoard to the Trainer to analyze our model's training curves."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "-APzqz2NeAyj"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "log_dir = os.path.join(model_artifact_dir, 'logs')\n",
        "\n",
        "%load_ext tensorboard\n",
        "%tensorboard --logdir {log_dir}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "FmPftrv0lEQy"
      },
      "source": [
        "### Evaluator\n",
        "The `Evaluator` component computes model performance metrics over the evaluation set. It uses the [TensorFlow Model Analysis](https://www.tensorflow.org/tfx/model_analysis/get_started) library. The `Evaluator` can also optionally validate that a newly trained model is better than the previous model. This is useful in a production pipeline setting where you may automatically train and validate a model every day. In this notebook, we only train one model, so the `Evaluator` automatically will label the model as \"good\". \n",
        "\n",
        "`Evaluator` will take as input the data from `ExampleGen`, the trained model from `Trainer`, and slicing configuration. The slicing configuration allows you to slice your metrics on feature values (e.g. how does your model perform on taxi trips that start at 8am versus 8pm?). See an example of this configuration below:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "fVhfzzh9PDEx"
      },
      "outputs": [],
      "source": [
        "eval_config = tfma.EvalConfig(\n",
        "    model_specs=[\n",
        "        # This assumes a serving model with signature 'serving_default'. If\n",
        "        # using estimator based EvalSavedModel, add signature_name: 'eval' and \n",
        "        # remove the label_key.\n",
        "        tfma.ModelSpec(label_key='tips')\n",
        "    ],\n",
        "    metrics_specs=[\n",
        "        tfma.MetricsSpec(\n",
        "            # The metrics added here are in addition to those saved with the\n",
        "            # model (assuming either a keras model or EvalSavedModel is used).\n",
        "            # Any metrics added into the saved model (for example using\n",
        "            # model.compile(..., metrics=[...]), etc) will be computed\n",
        "            # automatically.\n",
        "            # To add validation thresholds for metrics saved with the model,\n",
        "            # add them keyed by metric name to the thresholds map.\n",
        "            metrics=[\n",
        "                tfma.MetricConfig(class_name='ExampleCount'),\n",
        "                tfma.MetricConfig(class_name='BinaryAccuracy',\n",
        "                  threshold=tfma.MetricThreshold(\n",
        "                      value_threshold=tfma.GenericValueThreshold(\n",
        "                          lower_bound={'value': 0.5}),\n",
        "                      change_threshold=tfma.GenericChangeThreshold(\n",
        "                          direction=tfma.MetricDirection.HIGHER_IS_BETTER,\n",
        "                          absolute={'value': -1e-10})))\n",
        "            ]\n",
        "        )\n",
        "    ],\n",
        "    slicing_specs=[\n",
        "        # An empty slice spec means the overall slice, i.e. the whole dataset.\n",
        "        tfma.SlicingSpec(),\n",
        "        # Data can be sliced along a feature column. In this case, data is\n",
        "        # sliced along feature column trip_start_hour.\n",
        "        tfma.SlicingSpec(feature_keys=['trip_start_hour'])\n",
        "    ])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "9mBdKH1F8JuT"
      },
      "source": [
        "Next, we give this configuration to `Evaluator` and run it."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "Zjcx8g6mihSt"
      },
      "outputs": [],
      "source": [
        "# Use TFMA to compute a evaluation statistics over features of a model and\n",
        "# validate them against a baseline.\n",
        "\n",
        "# The model resolver is only required if performing model validation in addition\n",
        "# to evaluation. In this case we validate against the latest blessed model. If\n",
        "# no model has been blessed before (as in this case) the evaluator will make our\n",
        "# candidate the first blessed model.\n",
        "model_resolver = ResolverNode(\n",
        "      instance_name='latest_blessed_model_resolver',\n",
        "      resolver_class=latest_blessed_model_resolver.LatestBlessedModelResolver,\n",
        "      model=Channel(type=Model),\n",
        "      model_blessing=Channel(type=ModelBlessing))\n",
        "context.run(model_resolver)\n",
        "\n",
        "evaluator = Evaluator(\n",
        "    examples=example_gen.outputs['examples'],\n",
        "    model=trainer.outputs['model'],\n",
        "    baseline_model=model_resolver.outputs['model'],\n",
        "    # Change threshold will be ignored if there is no baseline (first run).\n",
        "    eval_config=eval_config)\n",
        "context.run(evaluator)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "AeCVkBusS_8g"
      },
      "source": [
        "Now let's examine the output artifacts of `Evaluator`. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "k4GghePOTJxL"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "evaluator.outputs"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Y5TMskWe9LL0"
      },
      "source": [
        "Using the `evaluation` output we can show the default visualization of global metrics on the entire evaluation set."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "U729j5X5QQUQ"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "context.show(evaluator.outputs['evaluation'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "t-tI4p6m-OAn"
      },
      "source": [
        "To see the visualization for sliced evaluation metrics, we can directly call the TensorFlow Model Analysis library."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "pyis6iy0HLdi"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "import tensorflow_model_analysis as tfma\n",
        "\n",
        "# Get the TFMA output result path and load the result.\n",
        "PATH_TO_RESULT = evaluator.outputs['evaluation'].get()[0].uri\n",
        "tfma_result = tfma.load_eval_result(PATH_TO_RESULT)\n",
        "\n",
        "# Show data sliced along feature column trip_start_hour.\n",
        "tfma.view.render_slicing_metrics(\n",
        "    tfma_result, slicing_column='trip_start_hour')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "7uvYrUf2-r_6"
      },
      "source": [
        "This visualization shows the same metrics, but computed at every feature value of `trip_start_hour` instead of on the entire evaluation set.\n",
        "\n",
        "TensorFlow Model Analysis supports many other visualizations, such as Fairness Indicators and plotting a time series of model performance. To learn more, see [the tutorial](https://www.tensorflow.org/tfx/tutorials/model_analysis/tfma_basic)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "TEotnkxEswUb"
      },
      "source": [
        "Since we added thresholds to our config, validation output is also available. The precence of a `blessing` artifact indicates that our model passed validation. Since this is the first validation being performed the candidate is automatically blessed."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "FZmiRtg6TKtR"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "blessing_uri = evaluator.outputs.blessing.get()[0].uri\n",
        "!ls -l {blessing_uri}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "hM1tFkOVSBa0"
      },
      "source": [
        "Now can also verify the success by loading the validation result record:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "lxa5G08bSJ8a"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "PATH_TO_RESULT = evaluator.outputs['evaluation'].get()[0].uri\n",
        "print(tfma.load_validation_result(PATH_TO_RESULT))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "T8DYekCZlHfj"
      },
      "source": [
        "### Pusher\n",
        "The `Pusher` component is usually at the end of a TFX pipeline. It checks whether a model has passed validation, and if so, exports the model to `_serving_model_dir`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "r45nQ69eikc9"
      },
      "outputs": [],
      "source": [
        "pusher = Pusher(\n",
        "    model=trainer.outputs['model'],\n",
        "    model_blessing=evaluator.outputs['blessing'],\n",
        "    push_destination=pusher_pb2.PushDestination(\n",
        "        filesystem=pusher_pb2.PushDestination.Filesystem(\n",
        "            base_directory=_serving_model_dir)))\n",
        "context.run(pusher)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "ctUErBYoTO9I"
      },
      "source": [
        "Let's examine the output artifacts of `Pusher`. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "pRkWo-MzTSss"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "pusher.outputs"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "peH2PPS3VgkL"
      },
      "source": [
        "In particular, the Pusher will export your model in the SavedModel format, which looks like this:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "4zyIqWl9TSdG"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "\n",
        "push_uri = pusher.outputs.model_push.get()[0].uri\n",
        "model = tf.saved_model.load(push_uri)\n",
        "\n",
        "for item in model.signatures.items():\n",
        "  pp.pprint(item)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "3-YPNUuHANtj"
      },
      "source": [
        "We're finished our tour of built-in TFX components!\n",
        "\n",
        "After you're happy with experimenting with TFX components and code in this notebook, you may want to export it as a pipeline to be orchestrated with Apache Airflow or Apache Beam. See the final section."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "qGNDOG1o1Tht"
      },
      "source": [
        "## Export to pipeline\n",
        "\n",
        "To export the contents of this notebook as a pipeline to be orchestrated with Airflow or Beam, follow the instructions below.\n",
        "\n",
        "If you're using Colab, make sure to **save this notebook to Google Drive** (`File` → `Save a Copy in Drive`) before exporting."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "DDbff6EdQ0iJ"
      },
      "source": [
        "### 1. Mount Google Drive (Colab-only)\n",
        "\n",
        "If you're using Colab, this notebook needs to mount your Google Drive to be able to access its own `.ipynb` file."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "cellView": "both",
        "colab": {},
        "colab_type": "code",
        "id": "CH8yu7Un1Thu"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "#docs_infra: no_execute\n",
        "\n",
        "#@markdown Run this cell and enter the authorization code to mount Google Drive.\n",
        "\n",
        "import sys\n",
        "\n",
        "if 'google.colab' in sys.modules:\n",
        "  # Colab.\n",
        "  from google.colab import drive\n",
        "\n",
        "  drive.mount('/content/drive')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "iFrJ8nOIRIJ0"
      },
      "source": [
        "### 2. Select an orchestrator"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "cellView": "both",
        "colab": {},
        "colab_type": "code",
        "id": "CO7erulbvUNi"
      },
      "outputs": [],
      "source": [
        "_runner_type = 'beam' #@param [\"beam\", \"airflow\"]\n",
        "_pipeline_name = 'chicago_taxi_%s' % _runner_type"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "d64gdS2u1Thw"
      },
      "source": [
        "### 3. Set up paths for the pipeline"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "X_dZL1lS1Thx"
      },
      "outputs": [],
      "source": [
        "#docs_infra: no_execute\n",
        "# For Colab notebooks only.\n",
        "# TODO(USER): Fill out the path to this notebook.\n",
        "_notebook_filepath = (\n",
        "    '/content/drive/My Drive/Colab Notebooks/Copy of components_keras.ipynb')\n",
        "\n",
        "# For Jupyter notebooks only.\n",
        "# _notebook_filepath = os.path.join(os.getcwd(),\n",
        "#                                   'taxi_pipeline_interactive.ipynb')\n",
        "\n",
        "# TODO(USER): Fill out the paths for the exported pipeline.\n",
        "_tfx_root = os.path.join(os.environ['HOME'], 'tfx')\n",
        "_taxi_root = os.path.join(os.environ['HOME'], 'taxi')\n",
        "_serving_model_dir = os.path.join(_taxi_root, 'serving_model')\n",
        "_data_root = os.path.join(_taxi_root, 'data', 'simple')\n",
        "_pipeline_root = os.path.join(_tfx_root, 'pipelines', _pipeline_name)\n",
        "_metadata_path = os.path.join(_tfx_root, 'metadata', _pipeline_name,\n",
        "                              'metadata.db')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "stNBJAIPvUNq"
      },
      "source": [
        "### 4. Choose components to include in the pipeline"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "DNc0Iks2vUNq"
      },
      "outputs": [],
      "source": [
        "#docs_infra: no_execute\n",
        "# TODO(USER): Specify components to be included in the exported pipeline.\n",
        "components = [\n",
        "    example_gen, statistics_gen, schema_gen, example_validator, transform,\n",
        "    trainer, evaluator, pusher\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "6nATTNYZ1Thy"
      },
      "source": [
        "### 5. Generate pipeline files"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "cellView": "both",
        "colab": {},
        "colab_type": "code",
        "id": "SsfNFi6iHMSp"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "#docs_infra: no_execute\n",
        "\n",
        "#@markdown Run this cell to generate the pipeline files.\n",
        "\n",
        "if get_ipython().magics_manager.auto_magic:\n",
        "  print('Warning: %automagic is ON. Line magics specified without the % prefix '\n",
        "        'will not be scrubbed during export to pipeline.')\n",
        "\n",
        "_pipeline_export_filepath = 'export_%s.py' % _pipeline_name\n",
        "context.export_to_pipeline(notebook_filepath=_notebook_filepath,\n",
        "                           export_filepath=_pipeline_export_filepath,\n",
        "                           runner_type=_runner_type)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "qL4RQQwSSt0y"
      },
      "source": [
        "### 6. Download pipeline files"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "cellView": "both",
        "colab": {},
        "colab_type": "code",
        "id": "FeRJyHly1Th3"
      },
      "outputs": [],
      "source": [
        "%%skip_for_export\n",
        "#docs_infra: no_execute\n",
        "\n",
        "#@markdown Run this cell to download the pipeline files as a `.zip`.\n",
        "\n",
        "if 'google.colab' in sys.modules:\n",
        "  from google.colab import files\n",
        "  import zipfile\n",
        "\n",
        "  zip_export_path = os.path.join(\n",
        "      tempfile.mkdtemp(), 'export.zip')\n",
        "  with zipfile.ZipFile(zip_export_path, mode='w') as export_zip:\n",
        "    export_zip.write(_pipeline_export_filepath)\n",
        "    export_zip.write(_taxi_constants_module_file)\n",
        "    export_zip.write(_taxi_transform_module_file)\n",
        "    export_zip.write(_taxi_trainer_module_file)\n",
        "\n",
        "  files.download(zip_export_path)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Po3wc1dMTJHw"
      },
      "source": [
        "To learn how to run the orchestrated pipeline with Apache Airflow, please refer to the [TFX Orchestration Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/airflow_workshop)."
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [
        "wdeKOEkv1Fe8"
      ],
      "name": "components_keras.ipynb",
      "private_outputs": true,
      "provenance": [],
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
